// Ninf-G-XX, ver. 0.1
// Copyright 2011 National Institute of Advanced Industrial Science and Technology
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Arg.hpp"
#include "CallException.hpp"
#include "GrpcDataImpl.hpp"
#include "GrpcData.hpp"
#include "grpc_data.h"
#include "URI.hpp"
#include "GrpcException.hpp"
#include "ByteArrayInputStream.hpp"
#include "ByteArrayOutputStream.hpp"
#include "Deleter.hpp"
#include <boost/foreach.hpp>
#include "ProtocolFactory.hh"
#include "Protocol.hh"


// For DIET interop test
#ifdef ISGC_TEST
#define USE_CURL
#include "remote_matrix.hh"

// Endianness management
inline char system_endianness() {
  union { 
    int int_val;
    char c_val[sizeof(int)];
  } bg_endian = {0x01020304};
  
  if (bg_endian.c_val[0]==1)
    return BIGENDIAN;
  return LTLENDIAN;
}

matrix_t* _newMatrixArg(const unsigned int& nb_col,
                     const unsigned int& nb_row,
		     char * ptr)
{
  matrix_t* result = new matrix_t;
  result->desc.nb_col = nb_col;
  result->desc.nb_row = nb_row;
  result->desc.elt_size = sizeof(double);
  result->desc.order = ROWMAJOR;
  result->desc.endianness = system_endianness();
  result->offset = 0;
  result->data = ptr;
  
  return result;
}

#endif


// just to avoid link error
ninf::data::MappingManager ninf::data::mappingManager;
std::vector<void *> ninf::data::grpcDataVector;

static bool isGrpcData(void * p) {
std::vector<void *>::iterator result =
    find(ninf::data::grpcDataVector.begin(),
         ninf::data::grpcDataVector.end(), p);
  return result != ninf::data::grpcDataVector.end();
}

using namespace std;
using namespace ninf::lib;

Arg::Arg():gdata(NULL),gdata_allocated_here(false),nDim(0){}

Arg::~Arg(){
  if (gdata != NULL && gdata_allocated_here)
    delete gdata;
  delete shape;
}

/**
 * create 'fake' one
 */
Arg::Arg(ninf::data::GrpcData * gdata, void * ptr):
  gdata(gdata), gdata_allocated_here(false){

  switch (gdata->variable_type) {
  case GRPC_INT:
  case GRPC_ARRAY_OF_INT:
  case GRPC_MATRIX_OF_INT:
    dataType = DT_INT;
    break;
  case GRPC_DOUBLE:
  case GRPC_ARRAY_OF_DOUBLE:
  case GRPC_MATRIX_OF_DOUBLE:
    dataType = DT_DOUBLE;
    break;
  case GRPC_COMPLEX:
  case GRPC_ARRAY_OF_COMPLEX:
  case GRPC_MATRIX_OF_COMPLEX:
    dataType = DT_DCOMPLEX;
    break;
  case GRPC_STRING:
    dataType = DT_STRING_TYPE;
  case GRPC_FILE:
    dataType = DT_FILENAME;
    break;
  case GRPC_CONTAINER_OF_GRPC_DATA:
  default:
    throw GRPC_EXCEPTION("unsupported datatype", GRPC_OTHER_ERROR_CODE);
  }
  v.p      = ptr;
  modeType = MODE_INOUT;
  nDim = gdata->variable_configuration.size();

  /// setup shape
  shape = new ArgShape(gdata->variable_configuration);
}


char *
Arg :: writeDataRecursive(DataOutputStream & dos, int dimCounter, char * p){
  ArgDim * dim = shape->dim[dimCounter];
  if (dimCounter == nDim - 1){
    for (int i = dim->start; i < dim->end; i += dim->step){
      char * p0 = p + (i * DataSize[(int)dataType]);
      switch (dataType){
      case DT_CHAR:
        dos.writeChar  (*((char *)p0));       break;
      case DT_SHORT:
        dos.writeShort (*((short *)p0));      break;
      case DT_INT:
        dos.writeInt   (*((int *)p0));        break;
      case DT_LONG:
        dos.writeLong  (*((long *)p0));       break;
      case DT_FLOAT:
        dos.writeFloat (*((float *)p0));      break;
      case DT_DOUBLE:
        dos.writeDouble(*((double *)p0));     break;
      case DT_STRING_TYPE:
        dos.writeString(*((char **)p0));      break;
      case DT_SCOMPLEX:
        dos.writeFloat (*((float *)p0));
        dos.writeFloat (*(((float *)p0) + 1)); break;
      case DT_DCOMPLEX:
        dos.writeDouble (*((double *)p0));
        dos.writeDouble (*(((double *)p0) + 1)); break;
      default:
        EXCEPTION(string("type ") + DataNames[dataType]
                                              + "is not supported") ;
      }
    }
    return p += dim->size * DataSize[(int)dataType];
  } else {
    for (int i = dim->start; i < dim->end; i += dim->step){
      p = writeDataRecursive(dos, dimCounter + 1, p);
    }
    return p;
  }
}

bool
Arg :: toBeWriteOut(){
  return gdata->immediate != NULL;
}

void
Arg :: writeData(DataOutputStream & dos){
  if (nDim == 0) {  // should be reconsider...
    writeScalar(dos);
    return;
  }
  dos.writeInt((int)dataType);
  dos.writeInt((int)modeType);
  shape->writeOut(dos);

  writeDataRecursive(dos, 0, (char *)gdata->immediate);
#if 0
  // short hack
  if (! isGrpcData(v.p)) { // v.p is not grpcData
    writeDataRecursive(dos, 0, (char *)v.p);
  } else {
    // ok, it is a grpc_data..!
    grpc_data_t * data = (grpc_data_t *) v.p;
    ninf::data::GrpcData * realData = (ninf::data::GrpcData *)data->realhandle;
    void * ptr = realData->getLocalMemPointer();
    writeDataRecursive(dos, 0, (char *)ptr);
  }
#endif
}

// TODO  v.p = gdata->immediate; ????
void
Arg :: writeScalar(DataOutputStream & dos){
  dos.writeInt((int)dataType);
  dos.writeInt((int)modeType);
  switch (dataType){
  case DT_CHAR:
    dos.writeChar  (v.c);         break;
  case DT_SHORT:
    dos.writeShort (v.s);         break;
  case DT_INT:
    dos.writeInt   (v.i);         break;
  case DT_LONG:
    dos.writeLong  (v.l);         break;
  case DT_FLOAT:
    dos.writeFloat (v.f);         break;
  case DT_DOUBLE:
    dos.writeDouble(v.d);         break;
  case DT_STRING_TYPE:
    dos.writeString((char *)v.p); break;
  case DT_FILENAME:
    dos.writeString((char *)v.p); break;
  default:
    EXCEPTION(string("type ") + DataNames[dataType]
	      + "is not supported for scalar") ;
  }
}

/****************************************************************/
/****************************************************************/

char *
Arg :: readDataRecursive(DataInputStream & dis, int dimCounter, char * p){
  ArgDim * dim = shape->dim[dimCounter];
  if (dimCounter == nDim - 1){
    for (int i = dim->start; i < dim->end; i += dim->step){
      char * p0 = p + (i * DataSize[(int)dataType]);
      switch (dataType){
      case DT_CHAR:
	(*((char *)p0)) = dis.readChar();        break;
      case DT_SHORT:
	(*((short *)p0)) = dis.readShort();      break;
      case DT_INT:
	(*((int *)p0))   = dis.readInt();        break;
      case DT_LONG:
	(*((long *)p0))  = dis.readLong();       break;
      case DT_FLOAT:
	(*((float *)p0)) = dis.readFloat();      break;
      case DT_DOUBLE:
	(*((double *)p0))= dis.readDouble();     break;
      case DT_STRING_TYPE:
	(*((char **)p0)) = dis.readString();     break;
      case DT_SCOMPLEX:
	(*((float *)p0)) =       dis.readFloat();
	(*(((float *)p0) + 1)) = dis.readFloat();  break;
      case DT_DCOMPLEX:
	(*((double *)p0))=       dis.readDouble();
	(*(((double *)p0) + 1))= dis.readDouble(); break;
      default:
	EXCEPTION(string("type ") + DataNames[dataType]
		  + "is not supported") ;
      }
    }
    return p += dim->size * DataSize[(int)dataType];
  } else {
    for (int i = dim->start; i < dim->end; i += dim->step){
      p = readDataRecursive(dis, dimCounter + 1, p);
    }
    return p;
  }
}

void
Arg :: readData(DataInputStream & dis) {
  if (nDim == 0) {
    readScalar(dis);
    return;
  }
  dataType = (DataType)dis.readInt();
  modeType = (ModeType)dis.readInt();
  ArgShape tmpShape(dis);
  if (!shape->compareWith(tmpShape))
    EXCEPTION("header shape is different from the one expected");
  readDataRecursive(dis, 0, (char *)gdata->immediate);
}

void
Arg :: writeToGdata() {

#ifdef ISGC_TEST
  URI uri(gdata->outputURIs.front());

  if (gdata->variable_configuration.size() == 2) {
    printf("size = 2\n");
    std::cout << uri.scheme << std::endl;
    if (uri.scheme == "http") {
      std::cout << gdata->variable_configuration[0] << ","
		<< gdata->variable_configuration[1] << std::endl;

      matrix_t * mat = 
	_newMatrixArg(gdata->variable_configuration[0],
		      gdata->variable_configuration[1],
		      (char *) v.p);

      URI org = gdata->outputURIs.front();
      URI mod(org);
      size_t lastSlash = mod.body.find_last_of("/");
      std::string name = mod.body.substr(lastSlash+1);
      
      mod.body = mod.body.substr(0, lastSlash) + std::string("/upload.php");
      put_matrix(mod.toString(), name, *mat);
      std::cout << mod << std::endl;
      std::cout << name << std::endl;

      delete mat; // data will not be free'd
      return;
    }
  }
#endif 

  // write to byteArrayStream
  ByteArrayOutputStream bos(10000, 10000);
  DataOutputStream dos(&bos);
  writeData(dos);

  BOOST_FOREACH(ninf::lib::URI uri, gdata->outputURIs) {
    // create protocol
    ProtocolFactory factory;
    Protocol * p = factory.getProtocol(uri.scheme);
    Deleter<Protocol> _d(p);
    // upload the data
    try {
      std::cerr << uri << std::endl;
      std::cerr << uri.toString() << std::endl;
      p->uploadDataMemory(uri.toString(), bos.getBytes(), bos.length());
    } catch (ProtocolError e) {
      GRPC_EXCEPTION("failed to upload data", GRPC_OTHER_ERROR_CODE);
    }
  }
}

void
Arg :: readFromGdata() {
  // pick up the first in data;
  URI uri(gdata->inputURIs.front());
  
#ifdef ISGC_TEST
  if (gdata->variable_configuration.size() == 2) {
    printf("size = 2\n");
    std::cout << uri.scheme << std::endl;
    if (uri.scheme == "http") {
      //      std::cout << gdata->variable_configuration[0] << ","
      //<< gdata->variable_configuration[1] << std::endl;

      matrix_t mat;
      mat.offset = 0;

      URI uri = gdata->inputURIs.front();
      std::string path = uri.path;
      // no need to modify
      //      uri.body = std::string("tmp") + "/" + path;

      get_matrix(uri.toString(), mat);

      std::cout << "data = " ;
      for (int i= 0; i < mat.desc.nb_col * mat.desc.nb_row; i++) 
	std::cout << *(((double *)(mat.data)) + i) << ", ";
      std::cout << std::endl;
      v.p = mat.data;

      return;
    }
  }
#endif 

  // create protocol
  ProtocolFactory factory;
  Protocol * p = factory.getProtocol(uri.scheme);
  Deleter<Protocol> _d(p);
  // download the data
  char * ptr;
  size_t size;
  try {
    p->downloadDataMemory(uri.toString(), (void **)&ptr, &size);
  } catch (ProtocolError e) {
    GRPC_EXCEPTION("failed to download data", GRPC_OTHER_ERROR_CODE);
  }
    // write to byteArrayStream
  ByteArrayInputStream bis(ptr, size);
  DataInputStream dis(&bis);
  readData(dis);

  free(ptr);
}

void
Arg :: readScalar(DataInputStream & dis){
  dataType = (DataType)dis.readInt();
  modeType = (ModeType)dis.readInt();
  switch (dataType){
  case DT_CHAR:
    v.c = dis.readChar();         break;
  case DT_SHORT:
    v.s = dis.readShort();        break;
  case DT_INT:
    v.i = dis.readInt();          break;
  case DT_LONG:
    v.l = dis.readLong();         break;
  case DT_FLOAT:
    v.f = dis.readFloat();        break;
  case DT_DOUBLE:
    v.d = dis.readDouble();       break;
  case DT_STRING_TYPE:
    v.p = dis.readString();       break;
  case DT_FILENAME:
    v.p = dis.readString();       break;
  default:
    EXCEPTION(string("type ") + DataNames[dataType]
	      + "is not supported for scalar") ;
  }
}


void
Arg :: setupShape(ParamDesc * desc, int * iArgs, int len){
  shape = new ArgShape(desc, iArgs, len);
}


void
Arg :: setupGdata(){
  // if the arg is backed with gdata, set it in the field
  // else, make up gdata and put it to the field.
  if (isGrpcData(v.p)){
    gdata = (ninf::data::GrpcData *)((grpc_data_t*)v.p)->realhandle;
    gdata_allocated_here = false;
    void * ptrIn = gdata->getLocalMemPointer();
    void * ptrOut = gdata->getLocalOutMemPointer();
    if (ptrIn != NULL) {
      gdata->immediate = ptrIn;
    } else if (ptrOut != NULL) {
      gdata->immediate = ptrOut;
    }
  } else {
    // ok, have to create one,
    gdata = new ninf::data::GrpcData();
    gdata_allocated_here = true;
    gdata->immediate = v.p;
    for (int i = 0; i < shape->nDim; i++)
      gdata->variable_configuration.push_back((size_t)shape->dim[i]->size);

    if (dataType == DT_INT) {
      switch (nDim) {
      case 0: gdata->variable_type = GRPC_INT; break;
      case 1: gdata->variable_type = GRPC_ARRAY_OF_INT; break;
      case 2: gdata->variable_type = GRPC_MATRIX_OF_INT; break;
      default: goto error;
      }
      return;
    }
    if (dataType == DT_DOUBLE) {
      switch (nDim) {
      case 0: gdata->variable_type = GRPC_DOUBLE; break;
      case 1: gdata->variable_type = GRPC_ARRAY_OF_DOUBLE; break;
      case 2: gdata->variable_type = GRPC_MATRIX_OF_DOUBLE; break;
      default: goto error;
      }
      return;
    }
    if (dataType == DT_DCOMPLEX) {
      switch (nDim) {
      case 0: gdata->variable_type = GRPC_COMPLEX; break;
      case 1: gdata->variable_type = GRPC_ARRAY_OF_COMPLEX; break;
      case 2: gdata->variable_type = GRPC_MATRIX_OF_COMPLEX; break;
      default: goto error;
      }
      return;
    }
    if (dataType == DT_STRING_TYPE && nDim == 0)
      gdata->variable_type = GRPC_STRING;
    if (dataType == DT_FILENAME && nDim == 0)
      gdata->variable_type = GRPC_FILE;
error:
    throw GRPC_EXCEPTION("unknown type", GRPC_OTHER_ERROR_CODE);

  }
}

int
Arg :: asInt(){
  if (nDim == 0) {  // scalar
    switch (dataType){
    case DT_CHAR:
      return (int) v.c;
    case DT_SHORT:
      return (int) v.s;
    case DT_INT:
      return (int) v.i;
    case DT_LONG:
      return (int) v.l;
    case DT_FLOAT:
      return (int) v.f;
    case DT_DOUBLE:
      return (int) v.d;
    case DT_FILENAME:
      return -1;
    case DT_STRING_TYPE:
      return -1;
    default:
      CALL_EXCEPTION("the type is not supported for scalar arg",
		     NINF_INTERNAL_ERROR);
    }
  }
  // for vector arg
  return -1;
}

void
Arg :: allocate(bool shrink){
  int size = shape->calcSize(shrink) * DataSize[(int)dataType];
  v.p = malloc(size);
  if (v.p == NULL)
    EXCEPTION("failed to allocate memory for Arg ");
}
